/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: json_writer.cpp
 *
 * Purpose: json writer implementation
 *
 * Developer:
 *   wen.gu , 2019-07-08
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/

#include "panda/core/json.h"


#include <stack>
#include <math.h> 
#include <string.h>

 // Also support old flag NO_LOCALE_SUPPORT
#ifdef NO_LOCALE_SUPPORT
#define JSONCPP_NO_LOCALE_SUPPORT
#endif

#ifndef JSONCPP_NO_LOCALE_SUPPORT
#include <clocale>
#endif

#define LOG_TAG "jswr"
#include "panda/core/log.h"

namespace panda
{
namespace core
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/


/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

 /// Constant that specify the size of the buffer that must be passed to
 /// UnitToString.
static const uint32_t gUintToStringBufferSize = 3 * sizeof(uint64_t) + 1;

// Defines a char buffer for use with UnitToString().
typedef char UIntToStringBuffer[gUintToStringBufferSize];

struct MyNode
{
    const Value* mVal = nullptr;
    Value::ObjectMembers::const_iterator objIt;
    Value::ArrayElements::const_iterator arrIt;
};


using ValueNodes = std::stack<MyNode>;

enum class WriteState: uint8_t
{
    Start = 0,
    Object,
    Array,
    Error,
    End
};

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static inline char getDecimalPoint() 
{
#ifdef JSONCPP_NO_LOCALE_SUPPORT
    return '\0';
#else
    struct lconv* lc = localeconv();
    return lc ? *(lc->decimal_point) : '\0';
#endif
}

 /** Change ',' to '.' everywhere in buffer.
  *
  * We had a sophisticated way, but it did not work in WinCE.
  * @see https://github.com/open-source-parsers/jsoncpp/pull/9
  */
template <typename Iter> 
Iter fixNumericLocale(Iter begin, Iter end) 
{
    for (; begin != end; ++begin) 
    {
        if (*begin == ',') 
        {
            *begin = '.';
        }
    }
    return begin;
}

template <typename Iter> 
void fixNumericLocaleInput(Iter begin, Iter end) 
{
    char decimalPoint = getDecimalPoint();
    if ((decimalPoint == '\0') || (decimalPoint == '.')) 
    {
        return;
    }

    for (; begin != end; ++begin) 
    {
        if (*begin == '.') 
        {
            *begin = decimalPoint;
        }
    }
}

/**
 * Return iterator that would be the new end of the range [begin,end), if we
 * were to delete zeros in the end of string, but not the last zero before '.'.
 */
template <typename Iter> 
Iter fixZerosInTheEnd(Iter begin, Iter end) 
{
    for (; begin != end; --end) 
    {
        if (*(end - 1) != '0') 
        {
            return end;
        }
        // Don't delete the last zero before the decimal point.
        if (begin != (end - 1) && *(end - 2) == '.') 
        {
            return end;
        }
    }
    return end;
}


namespace 
{
 /** Converts an unsigned integer to string.
  * @param value Unsigned integer to convert to string
  * @param current Input/Output string buffer.
  *        Must have at least uintToStringBufferSize chars free.
  */
inline void UnitToString(uint64_t value, char*& current) 
{
    *--current = 0;
    do {
        *--current = static_cast<char>(value % 10U + static_cast<unsigned>('0'));
        value /= 10;
    } while (value != 0);
}


bool ValueToString(Value::LargestInt value, std::string& outStr)
{   
    UIntToStringBuffer buffer = { 0 };

    char* current = buffer + sizeof(buffer);
    char* pEnd = current;
    if (value == Value::minLargestInt) 
    {
        UnitToString(Value::LargestUInt(Value::maxLargestInt) + 1, current);
        *--current = '-';
    }
    else if (value < 0) 
    {
        UnitToString(Value::LargestUInt(-value), current);
        *--current = '-';
    }
    else 
    {
        UnitToString(Value::LargestUInt(value), current);
    }

    if (current >= buffer) /** todo refine me */
    {
       outStr.append(current, pEnd - current - 1);   
       return true;
    }
                     
    return false;
}

bool ValueToString(Value::LargestUInt value, std::string& outStr) 
{
    UIntToStringBuffer buffer;
    char* current = buffer + sizeof(buffer);
    char* pEnd = current;
    UnitToString(value, current);
    if(current >= buffer)  /** todo refine me? */
    {
        outStr.append(current, pEnd - current - 1);
        return true;
    }   

    return false;
}

inline bool ValueToString(bool value, std::string& outStr) 
{ 
    outStr += value ? "true" : "false"; 

    return true;
}


std::string ValueToString(double value, bool useSpecialFloats /*= false*/,
                          unsigned int precision /*= Value::defaultRealPrecision*/,
                          PrecisionType precisionType /*= PrecisionType::significantDigits*/)
{
    // Print into the buffer. We need not request the alternative representation
    // that always has a decimal point because JSON doesn't distinguish the
    // concepts of reals and integers.
    if (!isfinite(value)) 
    {
        static const char* const reps[2][3] = { {"NaN", "-Infinity", "Infinity"},
                                                {"null", "-1e+9999", "1e+9999"} };
        return reps[useSpecialFloats ? 0 : 1]
            [isnan(value) ? 0 : (value < 0) ? 1 : 2];
    }

    std::string buffer(size_t(36), '\0');

    while (true) 
    {
        int len = std::snprintf(
            &*buffer.begin(), buffer.size(),
            (precisionType == PrecisionType::significantDigits) ? "%.*g" : "%.*f",
            precision, value);
        assert(len >= 0);
        auto wouldPrint = static_cast<size_t>(len);
        if (wouldPrint >= buffer.size()) 
        {
            buffer.resize(wouldPrint + 1);
            continue;
        }
        buffer.resize(wouldPrint);
        break;
    }

    buffer.erase(fixNumericLocale(buffer.begin(), buffer.end()), buffer.end());

    // strip the zero padding from the right
    if (precisionType == PrecisionType::decimalPlaces) {
        buffer.erase(fixZerosInTheEnd(buffer.begin(), buffer.end()), buffer.end());
    }

    // try to ensure we preserve the fact that this was given to us as a double on
    // input
    if (buffer.find('.') == buffer.npos && buffer.find('e') == buffer.npos) {
        buffer += ".0";
    }

    return buffer;
}


inline bool ValueToString(double d, std::string& outStr)
{
    outStr += ValueToString(d, false, Value::defaultRealPrecision, PrecisionType::significantDigits);
    
    return true;
}


static unsigned int Utf8ToCodepoint(const char*& s, const char* e) {
    const unsigned int REPLACEMENT_CHARACTER = 0xFFFD;

    unsigned int firstByte = static_cast<unsigned char>(*s);

    if (firstByte < 0x80)
        return firstByte;

    if (firstByte < 0xE0) {
        if (e - s < 2)
            return REPLACEMENT_CHARACTER;

        unsigned int calculated =
            ((firstByte & 0x1F) << 6) | (static_cast<unsigned int>(s[1]) & 0x3F);
        s += 1;
        // oversized encoded characters are invalid
        return calculated < 0x80 ? REPLACEMENT_CHARACTER : calculated;
    }

    if (firstByte < 0xF0) {
        if (e - s < 3)
            return REPLACEMENT_CHARACTER;

        unsigned int calculated = ((firstByte & 0x0F) << 12) |
            ((static_cast<unsigned int>(s[1]) & 0x3F) << 6) |
            (static_cast<unsigned int>(s[2]) & 0x3F);
        s += 2;
        // surrogates aren't valid codepoints itself
        // shouldn't be UTF-8 encoded
        if (calculated >= 0xD800 && calculated <= 0xDFFF)
            return REPLACEMENT_CHARACTER;
        // oversized encoded characters are invalid
        return calculated < 0x800 ? REPLACEMENT_CHARACTER : calculated;
    }

    if (firstByte < 0xF8) {
        if (e - s < 4)
            return REPLACEMENT_CHARACTER;

        unsigned int calculated = ((firstByte & 0x07) << 18) |
            ((static_cast<unsigned int>(s[1]) & 0x3F) << 12) |
            ((static_cast<unsigned int>(s[2]) & 0x3F) << 6) |
            (static_cast<unsigned int>(s[3]) & 0x3F);
        s += 3;
        // oversized encoded characters are invalid
        return calculated < 0x10000 ? REPLACEMENT_CHARACTER : calculated;
    }

    return REPLACEMENT_CHARACTER;
}



static std::string ToHex16Bit(unsigned int x) 
{
 static const char hex2[] = 
     "000102030405060708090a0b0c0d0e0f"
    "101112131415161718191a1b1c1d1e1f"
    "202122232425262728292a2b2c2d2e2f"
    "303132333435363738393a3b3c3d3e3f"
    "404142434445464748494a4b4c4d4e4f"
    "505152535455565758595a5b5c5d5e5f"
    "606162636465666768696a6b6c6d6e6f"
    "707172737475767778797a7b7c7d7e7f"
    "808182838485868788898a8b8c8d8e8f"
    "909192939495969798999a9b9c9d9e9f"
    "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
    "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"
    "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
    "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"
    "e0e1e2e3e4e5e6e7e8e9eaebecedeeef"
    "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff";

    const unsigned int hi = (x >> 8) & 0xff;
    const unsigned int lo = x & 0xff;
    std::string result(4, ' ');
    result[0] = hex2[2 * hi];
    result[1] = hex2[2 * hi + 1];
    result[2] = hex2[2 * lo];
    result[3] = hex2[2 * lo + 1];
    return result;
}



bool ValueToString(const char* str, std::string& outStr)
{
    outStr += "\"";
    uint32_t len = strlen(str);
    const char* pStart = str;
    const char* pEnd = str + len;
    for (; pStart < pEnd; ++pStart)
    {
        switch (*pStart)
        {
        case '\"':
            outStr += "\\\"";
            break;
        case '\\':
            outStr += "\\\\";
            break;
        case '\b':
            outStr += "\\b";
            break;
        case '\f':
            outStr += "\\f";
            break;
        case '\n':
            outStr += "\\n";
            break;
        case '\r':
            outStr += "\\r";
            break;
        case '\t':
            outStr += "\\t";
            break;
        default:
            unsigned int cp = Utf8ToCodepoint(pStart, pEnd);
            // don't escape non-control characters
            // (short escape sequence are applied above)
            if (cp < 0x80 && cp >= 0x20)
                outStr += static_cast<char>(cp);
            else if (cp < 0x10000) { // codepoint is in Basic Multilingual Plane
                outStr += "\\u";
                outStr += ToHex16Bit(cp);
            }
            else { // codepoint is not in Basic Multilingual Plane
                  // convert to surrogate pair first
                cp -= 0x10000;
                outStr += "\\u";
                outStr += ToHex16Bit((cp >> 10) + 0xD800);
                outStr += "\\u";
                outStr += ToHex16Bit((cp & 0x3FF) + 0xDC00);
            }
            break;
        }
    }

    outStr += "\"";

    return true;
}


bool NormalValue2String(const Value& val, std::string& outStr)
{
    bool ret = true;
    switch (val.type()) 
    {
    case ValueType::Int:
        ret = ValueToString(val.asInt64(), outStr);
        break;
    case ValueType::Uint:
        ret = ValueToString(val.asUInt64(), outStr);
        break;
    case ValueType::Real:
        ret = ValueToString(val.asDouble(), outStr);
        break;
    case ValueType::Boolean:
        ret = ValueToString(val.asBool(), outStr);
        break;
    case ValueType::String:
        ret = ValueToString(val.asCString(), outStr);
        break;
    case ValueType::Null:
        outStr += "null";
        break;
    default:
        ret = false;
        break;
    }

    return ret;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


class MyWriter
{
public:
    MyWriter();
    ~MyWriter();

public:
    bool write(const Value& root, std::string& outStr, bool isStyled = false);


private:
    WriteState onWriteObject(std::string& outStr, bool isStyled);

    WriteState onWriteArray(std::string& outStr, bool isStyled);
    WriteState onWriteStart(const Value& root, std::string& outStr, bool isStyled);
    WriteState onWriteError(std::string& outStr);
private:
    /** true: break, false: continue */
    bool writeObjectStart(const Value& member, std::string& outStr, bool isStyled);
    bool writeArrayStart(const Value& member, std::string& outStr, bool isStyled);

    /**true: break, false: continue  */
    bool writeValue(const Value& member, std::string& outStr, WriteState& reStart, bool isStyled);

    WriteState processDemiliter(WriteState curState, WriteState targetState, 
                                MyNode& node, ValueType vt, std::string& outStr, bool isStyled);
private:
    inline MyNode& currentNode()
    {
        return mNodes.top();
    }

    inline void pushNode(MyNode& mn)
    {
        mNodes.push(mn);
    }

    inline void popNode()
    {
        mNodes.pop();
    }

    inline bool currentIsRoot()
    {
        return mNodes.empty();
    }

private:
    bool mIsErroOccur = false;
    ValueNodes mNodes;

};

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

MyWriter::MyWriter()
{
    /** todo something */
}

MyWriter::~MyWriter()
{
    /** todo something */
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

WriteState MyWriter::onWriteObject(std::string& outStr, bool isStyled)
{
    WriteState wState = WriteState::Object;

    while (wState == WriteState::Object)
    {   
        MyNode& node = currentNode();
        const Value::ObjectMembers& objms = node.mVal->getObjectMembers();
        Value::ObjectMembers::const_iterator& cit = node.objIt;

        while (cit != objms.cend())
        {
            const Value& member = cit->second;
            //ValueType vt = member.type();
            std::string key = "";
            if (ValueToString(cit->first.c_str(), key))
            {
                outStr += key + ":";

                if (writeValue(member, outStr, wState, isStyled))
                {
                    break;
                }
                else
                {
                    ++cit; /** do next loop */
                    if (cit != objms.cend())
                    {
                        outStr += isStyled ? ",\n" : ","; /** write member delimiter */
                    }
                }
            }
            else
            {
                LOGE("write object key failed\n");
                wState = WriteState::Error;
            }
        }

        wState = processDemiliter(wState, WriteState::Array, node, ValueType::Object, outStr, isStyled);
    } 

    return wState;
}



WriteState MyWriter::onWriteArray(std::string& outStr, bool isStyled)
{
    WriteState wState = WriteState::Array;
    while (wState == WriteState::Array)
    {
        MyNode& node = currentNode();
        const Value::ArrayElements& arres = node.mVal->getArrayElements();
        Value::ArrayElements::const_iterator& cit = node.arrIt;

        while (cit != arres.cend())
        {
            if (writeValue(*cit, outStr, wState, isStyled))
            {
                break;
            }
            else
            {
                ++cit; /** do next loop */
                if (cit != arres.cend())
                {
                    outStr +=isStyled ? ",\n" : ","; /** write member delimiter */
                }
            }
        }

        wState = processDemiliter(wState, WriteState::Object, node, ValueType::Array, outStr, isStyled);
    } 

    return wState;
}

WriteState MyWriter::onWriteStart(const Value& root, std::string& outStr, bool isStyled)
{
    WriteState wState = WriteState::Error;
    ValueType vt = root.type();
    outStr.clear();

    if (vt == ValueType::Array)
    {
        if (writeArrayStart(root, outStr, isStyled))
        {
            wState = WriteState::Array;
        }
        else
        {
            wState = WriteState::End;
        }
    }
    else if (vt == ValueType::Object)
    {
        if (writeObjectStart(root, outStr, isStyled))
        {
            wState = WriteState::Object;
         }
        else
        {
            wState = WriteState::End;
        }
    }
    else
    {
        LOGE("invalid value type(%d) for write to string\n", uint8_t(vt));
    }

    return wState;
}

WriteState MyWriter::onWriteError(std::string& outStr)
{
    mIsErroOccur = true;
    outStr.clear();
    return WriteState::End;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


bool MyWriter::writeObjectStart(const Value& member, std::string& outStr, bool isStyled)
{
    if (member.size() > 0)
    {/** if not empty object */
        outStr += isStyled ? "{\n" : "{";
        MyNode curNode;
        curNode.mVal = &member;
        curNode.objIt = member.getObjectMembers().cbegin();
        pushNode(curNode);
        return true;
    }
    else
    {
        /** if current is empty object, then finish it and continue */
        outStr += "{}";
    }

    return false;
}

bool MyWriter::writeArrayStart(const Value& member, std::string& outStr, bool isStyled)
{
    
    if (member.size() > 0)
    {/** if not empty array */ 
        outStr += isStyled ? "[\n" : "[";
        MyNode curNode;
        curNode.mVal = &member;
        curNode.arrIt = member.getArrayElements().cbegin();
        pushNode(curNode);
        return true;
    }
    else
    {
        /** if current is empty array, then finish it and continue */
        outStr += "[]";
    }

    return false;
}

bool MyWriter::writeValue(const Value& member, std::string& outStr, WriteState& retState, bool isStyled)
{
    bool ret = false;
    ValueType vt = member.type();

    if (vt == ValueType::Array)
    {
        if (writeArrayStart(member, outStr, isStyled))
        {
            ret = true;
            retState = WriteState::Array;
        }
    }
    else if (vt == ValueType::Object)
    {
        if (writeObjectStart(member, outStr, isStyled))
        {
            ret = true;
            retState = WriteState::Object;
        }
    }
    else
    {
        if (!NormalValue2String(member, outStr))
        {
            LOGE("write normal value failed\n");
            retState = WriteState::Error;
            ret = true;
        }
    }

    return ret;
}



WriteState MyWriter::processDemiliter(WriteState curState, WriteState targetState, MyNode& node, 
                                      ValueType vt, std::string& outStr, bool isStyled)
{
    WriteState retState = curState;
    if ((curState != targetState) && (curState != WriteState::Error))
    {
        bool isValEnd = false;
        const char* endCh = nullptr;
        if (vt == ValueType::Object)
        {
            isValEnd = (node.objIt == node.mVal->getObjectMembers().cend());
            endCh = isStyled ? "\n}" : "}";
        }
        else /** todo, is only object and array?? */
        {
            isValEnd = (node.arrIt == node.mVal->getArrayElements().cend());
            endCh = isStyled ? "\n]" : "]";
        } 

        if (isValEnd )
        {
            outStr += endCh; /** write boject end character */
            popNode(); /** pop node from stack, end current value write */

            if (currentIsRoot())
            {
                retState = WriteState::End;
            }
            else
            {
                MyNode& curNode = currentNode();
                const Value& curVal = *curNode.mVal;

                if (curVal.type() == ValueType::Object)
                {
                    ++curNode.objIt; /** skip current array value(one member of object) */
                    if (curNode.objIt != curVal.getObjectMembers().cend())
                    {
                        outStr += isStyled ? ",\n" : ","; /** write member demiliter */
                    }

                    retState = WriteState::Object;
                }
                else if (curVal.type() == ValueType::Array)
                {
                    ++curNode.arrIt; /** skip current array value(one element of array) */
                    if (curNode.arrIt != curVal.getArrayElements().cend())
                    {
                        outStr += isStyled ? ",\n" :","; /** write memeber demiliter */
                    }
                    retState = WriteState::Array;
                }
            }
        }
    }

    return retState;
}




bool MyWriter::write(const Value& root, std::string& outStr, bool isStyled /*= false*/)
{
    mIsErroOccur = false;
    WriteState wState = WriteState::Start;

    while (wState != WriteState::End)
    {
        switch (wState)
        {
        case WriteState::Start: wState = onWriteStart(root, outStr, isStyled); break;
        case WriteState::Object: wState = onWriteObject(outStr, isStyled); break;
        case WriteState::Array: wState = onWriteArray(outStr, isStyled); break;
        case WriteState::Error: wState = onWriteArray(outStr, isStyled); break;
        default:
            break;
        }
    }

    return !mIsErroOccur;
}

} // namespace




/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

std::string ValueToString(double val,
                                    unsigned int precision /*= Value::defaultRealPrecision*/,
                                    PrecisionType precisionType /*= PrecisionType::significantDigits*/)
{
    return ValueToString(val, false, precision, precisionType);
}


std::string ValueToString(int64_t val)
{
    std::string outStr = "";
    ValueToString(val, outStr);

    return outStr;
}

std::string ValueToString(uint64_t val)
{
    std::string outStr = "";
    ValueToString(val, outStr);

    return outStr;
}

/** \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format.
 * \param root Value to serialize.
 * \return true: success, false: failed.
 */
bool JsonWriteRaw(const Value& root, std::string& outStr)
{
    MyWriter mw;
    return mw.write(root, outStr);
}


/*
 * The rules for line breakand indent are as follow :
 *  -Object value :
 *      -if empty then print{} without indentand line break
 *      -if not empty the print '{', line break& indent, print one value per
 *       line and then unindentand line breakand print '}'.

 *  -Array value :
 *      -if empty then print[] without indentand line break
 *      -if the array contains no object value, empty array or some other
 *       value types, and all the values fit on one lines, then print the
 *       array on a single line.

 *  -otherwise,
        it the values do not fit on one line, or the array contains
 *      object or non empty array, then print one value per line.
 //////////////////////////////////////////////////////////////////////////////
 * \brief Serialize a Value in <a HREF="http://www.json.org">JSON</a> format
          with Styled way.
 * \param root Value to serialize.
 * \param outStr  std::string containing the JSON document that
 *                represents the root value
 * \return true: success, false: failed.

 */
bool JsonWriteStyled(const Value& root, std::string& outStr)
{
    MyWriter mw;
    return mw.write(root, outStr, true);
}

} /** namespace core */
} /** namespace panda */
